package com.jfinal.ext.ueditor.service;

import com.jfinal.core.JFinal;
import com.jfinal.ext.ueditor.config.*;
import com.jfinal.ext.ueditor.entity.FileInfo;
import com.jfinal.ext.ueditor.result.*;
import com.jfinal.ext.ueditor.storage.FileStorageManager;
import com.jfinal.ext.ueditor.storage.IStorageManager;
import com.jfinal.ext.ueditor.storage.StorageResult;
import com.jfinal.ext.util.ArrayUtil;
import com.jfinal.ext.util.FormatUtil;
import com.jfinal.ext.util.RatioUtil;
import com.jfinal.ext.util.UriFormatter;
import com.jfinal.ext.util.rand.MyUuidUtil;
import com.jfinal.kit.FileKit;
import com.jfinal.kit.StrKit;
import com.jfinal.log.Log;
import com.jfinal.upload.UploadFile;
import org.apache.commons.io.FileUtils;
import org.jetbrains.annotations.NotNull;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;

/**
 * @author Timor
 * @version 1.0
 * @date 2023/01/04 13:37:05
 */
public class UeditorService {
    private static final Log LOG = Log.getLog(UeditorService.class);
    public static final int MIN_START = 0;
    public static final String SCRAWL_FORMAT = "png";
    private final IStorageManager storageManager;


    public UeditorService() {
        this(new FileStorageManager());
    }

    public UeditorService(@NotNull IStorageManager storageManager) {
        this.storageManager = storageManager;
    }


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    public UploadResult uploadImage(@NotNull UploadFile uploadFile,
                                    @NotNull ImageUploadConfig config) {
        final String fileName = uploadFile.getFileName();
        final String fileExtension = FileKit.getFileExtension(fileName);

        if (config.getAllowFiles().stream().noneMatch(fileName::endsWith)) {
            return UploadResult.failure("不支持的图片类型：." + fileExtension);
        }


        final File file = uploadFile.getFile();

        if (config.getFixedRatio() && !checkImageRatio(file, config.getRatio())) {
            return UploadResult.failure("仅支持固定比例的图片：" + config.getRatio());
        }


        final FileInfo fileInfo = FileInfo.by(file, config.getName());
        fileInfo.setOriginalSize();

        final String path = FormatUtil.parse(config.getPathFormat(), Map.of(
                FormatUtil.FILE_NAME, fileInfo.getId(),
                FormatUtil.FILE_TYPE, fileInfo.getExtension(),
                FormatUtil.PREFIX, "",
                FormatUtil.SUFFIX, ""));

        final StorageResult result = storageManager.saveFile(file, UriFormatter.format(path));

        if (result.isFail()) {
            return UploadResult.failure(result.getMessage());
        }

        fileInfo.setNewName(result.getFileName());
        fileInfo.setUrl(result.getUrl());

        return ImageUploadResult.success(fileInfo);
    }

    public UploadResult uploadScrawl(@NotNull String scrawl,
                                     @NotNull ImageUploadConfig config) {
        if (StrKit.isBlank(scrawl)) {
            return UploadResult.failure("图片为空！");
        }


        final String path = FormatUtil.parse(config.getPathFormat(), Map.of(
                FormatUtil.FILE_NAME, MyUuidUtil.get(),
                FormatUtil.FILE_TYPE, SCRAWL_FORMAT,
                FormatUtil.PREFIX, ""));

        final byte[] bytes = Base64.getDecoder().decode(scrawl);
        final StorageResult ret = storageManager.saveBinaryFile(bytes, UriFormatter.format(path));

        if (ret.isFail()) {
            return UploadResult.failure(ret.getMessage());
        }


        final UploadResult result = UploadResult.success();

        final String fileName = ret.getFileName();

        result.setName(fileName);
        result.setTitle(ret.getFileNameWithoutExtension());
        result.setOriginal(fileName);
        result.setUrl(ret.getUrl());

        return result;
    }

    public UploadResult uploadScreenshot() {
        return UploadResult.failure("该功能已弃用！");
    }

    public UploadResult uploadVideo(@NotNull UploadFile uploadFile,
                                    @NotNull FileUploadConfig config) {
        return uploadFile(uploadFile, config);
    }

    public UploadResult uploadAudio() {
        return UploadResult.failure("该功能已弃用！");
    }

    public UploadResult uploadFile(@NotNull UploadFile uploadFile,
                                   @NotNull FileUploadConfig config) {
        final String fileName = uploadFile.getFileName();
        final String fileExtension = FileKit.getFileExtension(fileName);

        if (config.getAllowFiles().stream().noneMatch(fileName::endsWith)) {
            return UploadResult.failure("不支持的文件类型：." + fileExtension);
        }

        final File file = uploadFile.getFile();
        final FileInfo fileInfo = FileInfo.by(file, config.getName());

        final String path = FormatUtil.parse(config.getPathFormat(), Map.of(
                FormatUtil.FILE_NAME, fileInfo.getId(),
                FormatUtil.FILE_TYPE, fileInfo.getExtension(),
                FormatUtil.PREFIX, ""));

        final StorageResult result = storageManager.saveFile(file, UriFormatter.format(path));

        if (result.isFail()) {
            return UploadResult.failure(result.getMessage());
        }

        fileInfo.setNewName(result.getFileName());
        fileInfo.setUrl(result.getUrl());

        return UploadResult.success(fileInfo);
    }


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    public ListResult<CatchImageResult> catchImage(
            @NotNull List<String> source, @NotNull CatchImageConfig config) {

        if (source.isEmpty()) {
            return new ListResult<>(Collections.emptyList());
        }


        final List<CatchImageResult> result = new ArrayList<>(source.size());

        if (config.getEnable()) {
            final ImageHunter hunter = new ImageHunter(storageManager);

            source.forEach(item -> result.add(hunter.catchImage(item, config)));
        } else {
            source.forEach(item -> result.add(new CatchImageResult(item, item)));
        }

        return new ListResult<>(result);
    }


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    public ListFileResult<ImageRet> listImage(int start, int size,
                                              @NotNull ImageManagerConfig config) {
        return listFiles(start, size, config, file ->
                ImageRet.by(toUploadUrl(file, config)));
    }

    public ListFileResult<FileRet> listFile(int start, int size,
                                            @NotNull FileManagerConfig config) {
        return listFiles(start, size, config, file ->
                FileRet.by(toUploadUrl(file, config)));
    }


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    public <T extends FileRet> ListFileResult<T> listFiles(
            int start, int size,
            @NotNull FileManagerConfig config,
            @NotNull Function<File, T> func) {

        start = Math.max(start, MIN_START);

        if (size < 1) {
            size = config.getListSize();
        } else {
            size = Math.min(size, config.getListSize());
        }


        // todo 使用高性能算法
        final List<File> files = listFiles(config);

        if (start > files.size()) {
            return new ListFileResult<>(start, files.size(), List.of());
        }

        final int end = Math.min(start + size, files.size());

        final List<T> items = files.subList(start, end).stream().map(func).toList();

        return new ListFileResult<>(start, files.size(), items);
    }

    private List<File> listFiles(@NotNull FileManagerConfig config) {
        final List<String> dirs = config.getListPath();
        final List<String> allowFiles = config.getAllowFiles();

        if (dirs.isEmpty() || allowFiles.isEmpty()) {
            return Collections.emptyList();
        }

        return listFiles(dirs, allowFiles);
    }

    private List<File> listFiles(@NotNull List<String> dirs,
                                 @NotNull List<String> extensions) {
        final List<File> files = new LinkedList<>();

        final String[] types = ArrayUtil.castToArray(
                extensions.stream().map(item -> item.substring(1)));
        final String uploadPath = getBaseUploadPath();

        dirs.stream().map(dir -> new File(uploadPath, dir))
                .filter(file -> file.exists() && file.isDirectory())
                .forEach(directory ->
                        files.addAll(FileUtils.listFiles(directory, types, true)));
        return files;
    }


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    private boolean checkImageRatio(@NotNull File file,
                                    @NotNull String ratio) {
        try {
            final BufferedImage image = ImageIO.read(file);
            final int width = image.getWidth();
            final int height = image.getHeight();

            return RatioUtil.toRatio(width, height).equals(ratio);
        } catch (IOException e) {
            LOG.warn("加载读取图片失败：" + file, e);
        }
        return false;
    }


    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


    protected static String toUploadUrl(@NotNull File file,
                                        @NotNull IActionConfig config) {
        final String uploadPath = getBaseUploadPath();
        final String uri = file.getAbsolutePath()
                .substring(uploadPath.length());

        return UriFormatter.format(config.getUrlPrefix() + uri);
    }

    private static String getBaseUploadPath() {
        return JFinal.me().getConstants().getBaseUploadPath();
    }

}
